using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * A unary operator that calls a TemplateMethodModel.  It associates with the
     * <tt>Identifier</tt> or <tt>Dot</tt> to its left.
     */
    public sealed class MethodCall : Expression // NOTE: Was internal
    {
        private readonly Expression _target;
        private readonly ListLiteral _arguments;

        internal MethodCall(Expression target, List<Expression> arguments)
            : this(target, new ListLiteral(arguments))
        {
        }

        private MethodCall(Expression target, ListLiteral arguments)
        {
            _target = target;
            _arguments = arguments;
        }

        protected override ITemplateModel _getAsTemplateModel(Environment env)
        {
            ITemplateModel targetModel = _target.GetAsTemplateModel(env);
            if (targetModel is ITemplateMethodModel)
            {
                var targetMethod = (ITemplateMethodModel) targetModel;
                object result;
                if (targetMethod is ITemplateMethodModelEx)
                {
                    result = ((ITemplateMethodModelEx) targetMethod).Exec(_arguments.GetModelList(env));
                }
                else
                {
                    result = targetMethod.Exec(_arguments.GetValueList(env));
                }
                return env.GetObjectWrapper().Wrap(result);
            }
            if (targetModel is Macro)
            {
                var func = (Macro) targetModel;
                env.SetLastReturnValue(null);
                if (!func.IsFunction)
                {
                    throw new TemplateException("A macro cannot be called in an expression.", env);
                }
                TextWriter prevOut = env.GetOut();
                try
                {
                    env.SetOut(Environment.NullWriter);
                    env.Visit(func, null, _arguments.Values, null, null);
                }
                catch (IOException)
                {
                    throw new Exception("This should be impossible."); // NOTE: Was InternalError
                }
                finally
                {
                    env.SetOut(prevOut);
                }
                return env.GetLastReturnValue();
            }
            throw InvalidTypeException(targetModel, _target, env, "method");
        }

        public override string GetCanonicalForm()
        {
            var buf = new StringBuilder();
            buf.Append(_target.GetCanonicalForm());
            buf.Append("(");
            string list = _arguments.GetCanonicalForm();
            buf.Append(list.Substring(1, list.Length - 2));
            buf.Append(")");
            return buf.ToString();
        }

        private ITemplateModel GetConstantValue()
        {
            return null;
        }

        internal override bool IsLiteral()
        {
            return false;
        }

        protected override Expression DeepCloneImpl(string name, Expression subst)
        {
            return new MethodCall(_target.DeepClone(name, subst), (ListLiteral) _arguments.DeepClone(name, subst));
        }
    }
}